/*
 * Copyright 2008 Les Hazlewood
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.pojodm.party;

import org.pojodm.entity.Entity;

import java.util.*;

/**
 * A party is any &quot;contactable&quot; entity that can be associated with email addresses, postal
 * addresses or phone/fax numbers.  That is, a party can be a person, a group of people (such as a
 * business or organization) or any other similar entity.
 *
 * @author Les Hazlewood
 */
public abstract class Party extends Entity {

    protected TimeZone timeZone = TimeZone.getDefault();

    protected List<EmailAddress> emailAddresses;

    protected List<PostalAddress> postalAddresses;

    protected List<PhoneNumber> phoneNumbers;

    protected String url;

    public Party() {
    }

    /**
     * Returns the default timezone where this party is (usually) located.  If this party is an
     * organization or company, this timezone would be the organization's headquarter's timezone. If
     * this party is a person, this timezone would probably be the timezone where the person lives.
     *
     * @return the default timezone where this party is (usually) located.
     */
    public TimeZone getTimeZone() {
        return timeZone;
    }

    public void setTimeZone(TimeZone timeZone) {
        this.timeZone = timeZone;
    }

    public List<EmailAddress> getEmailAddresses() {
        return emailAddresses;
    }

    protected void setEmailAddresses(List<EmailAddress> emailAddresses) {
        this.emailAddresses = emailAddresses;
    }

    public List<PostalAddress> getPostalAddresses() {
        return postalAddresses;
    }

    protected void setPostalAddresses(List<PostalAddress> postalAddresses) {
        this.postalAddresses = postalAddresses;
    }

    public List<PhoneNumber> getPhoneNumbers() {
        return phoneNumbers;
    }

    protected void setPhoneNumbers(List<PhoneNumber> phoneNumbers) {
        this.phoneNumbers = phoneNumbers;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    /**
     * Adds an email address to this party's collection of {@link #getEmailAddresses() email
     * addresses}.
     * <p/>
     * <p>If the existing emailAddresses collection is <tt>null</tt>, a new collection will be
     * created and assigned to this party and then the email address will be added.
     * <p/>
     * <p>If the specified email address already exists in this party's collection, it will not be
     * added again.
     *
     * @param email the email address to add/associate with this party.
     */
    public void add(EmailAddress email) {
        List<EmailAddress> emails = getEmailAddresses();
        if (emails == null) {
            emails = new ArrayList<EmailAddress>();
            setEmailAddresses(emails);
        }
        if (!emails.contains(email)) {
            emails.add(email);
        }
    }

    /**
     * Removes the given email from this Party's email collection.
     *
     * @param email the email address to remove.
     * @return true if the email address was removed, false if it wasn't removed.
     */
    public boolean remove(EmailAddress email) {
        List<EmailAddress> emails = getEmailAddresses();
        return (emails != null && emails.remove(email));
    }

    /**
     * Returns whether or not the specified email address has already been associated with this
     * party.
     *
     * @param email the email to check for association
     * @return true if the specified email address has already been added to this party's
     *         emailAddresses collection.
     */
    public boolean has(EmailAddress email) {
        List<EmailAddress> emails = getEmailAddresses();
        return (emails != null && emails.contains(email));
    }

    public EmailAddress getEmailAddress(String label) {
        List<EmailAddress> emails = getEmailAddresses();
        if (label != null && emails != null) {
            for (EmailAddress ea : emails) {
                if (ea.getText().equals(label)) {
                    return ea;
                }
            }
        }
        return null;
    }

    /**
     * Adds an email address to this party's collection of {@link #getPostalAddresses() postal
     * addresses}.
     * <p/>
     * <p>If the existing postalAddresses collection is <tt>null</tt>, a new collection will be
     * created and assigned to this party and then the postal address will be added.
     * <p/>
     * <p>If the specified postal address already exists in this party's collection, it will not be
     * added again.
     *
     * @param pa the postal address to add/associate with this party.
     */
    public void add(PostalAddress pa) {
        List<PostalAddress> pas = getPostalAddresses();
        if (pas == null) {
            pas = new ArrayList<PostalAddress>();
            setPostalAddresses(pas);
        }
        if (!pas.contains(pa)) {
            pas.add(pa);
        }
    }

    /**
     * Removes the given postal address from this Party's postal addresses collection.
     *
     * @param pa the postal address to remove.
     * @return true if the postal address was removed, false if it wasn't removed.
     */
    public boolean remove(PostalAddress pa) {
        List<PostalAddress> pas = getPostalAddresses();
        return (pas != null && pas.remove(pa));
    }

    /**
     * Returns whether or not the specified postal address has already been associated with this
     * party.
     *
     * @param pa the postal address to check for association
     * @return true if the specified postal address has already been added to this party's
     *         postalAddresses collection.
     */
    public boolean has(PostalAddress pa) {
        List<PostalAddress> addresses = getPostalAddresses();
        return addresses != null && addresses.contains(pa);
    }

    /**
     * Adds an phone number to this party's collection of {@link #getPhoneNumbers() phone numbers}.
     * <p/>
     * <p>If the existing phoneNumbers collection is <tt>null</tt>, a new collection will be created
     * and assigned to this party and then the phone number will be added.
     * <p/>
     * <p>If the specified phone number already exists in this party's collection, it will not be
     * added again.
     *
     * @param pn the phone number to add/associate with this party.
     */
    public void add(PhoneNumber pn) {
        List<PhoneNumber> numbers = getPhoneNumbers();
        if (numbers == null) {
            numbers = new ArrayList<PhoneNumber>();
            setPhoneNumbers(numbers);
        }
        if (!numbers.contains(pn)) {
            numbers.add(pn);
        }
    }

    /**
     * Removes the given phone number from this Party's phone numbers collection.
     *
     * @param pn the phone number to remove.
     * @return true if the phone number was removed, false if it wasn't removed.
     */
    public boolean remove(PhoneNumber pn) {
        List<PhoneNumber> pns = getPhoneNumbers();
        return pns != null && pns.remove(pn);
    }

    /**
     * Returns whether or not the specified phone number has already been associated with this
     * party.
     *
     * @param pn the phone number to check for association
     * @return true if the specified phone number has already been added to this party's
     *         phoneNumbers collection.
     */
    public boolean has(PhoneNumber pn) {
        List<PhoneNumber> numbers = getPhoneNumbers();
        return numbers != null && numbers.contains(pn);
    }

    /**
     * Searches a phone number in the PhoneNumber collection using its label as search criteria
     *
     * @param label the label used as search criteria over the PhoneNumber collection
     * @return the first PhoneNumber found in the collection with a matching label, or
     *         <tt>null</tt> if there's no matching label in the collection
     */
    public PhoneNumber getPhoneNumber(String label) {
        List<PhoneNumber> numbers = getPhoneNumbers();
        if (label != null && numbers != null) {
            for (PhoneNumber n : numbers) {
                if (label.equals(n.getLabel())) {
                    return n;
                }
            }
        }
        return null;
    }

    public StringBuffer toStringBuffer() {
        StringBuffer sb = super.toStringBuffer();
        if (getTimeZone() != null) {
            sb.append(",timeZone=").append(getTimeZone());
        }
        return sb;
    }

    public boolean onEquals(Object o) {
        if (o instanceof Party) {
            Party p = (Party) o;
            return (getTimeZone() == null ? p.getTimeZone() == null : getTimeZone().equals(p.getTimeZone())) &&
                    (getUrl() == null ? p.getUrl() == null : getUrl().equals(p.getUrl()));
        }
        return false;
    }

    public int hashCode() {
        int result = timeZone != null ? timeZone.hashCode() : 0;
        result = 29 * result + (getUrl() != null ? getUrl().hashCode() : 0);
        return result;
    }

    /**
     * Performs a deep copy of the Party, cloning all children collections and their elements as well.
     */
    @Override
    @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException"})
    public Object clone() {
        Party clone = (Party) super.clone();

        clone.setTimeZone((TimeZone) getTimeZone().clone());

        List<EmailAddress> emails = getEmailAddresses();
        if (emails != null) {
            List<EmailAddress> emailClones = new ArrayList<EmailAddress>(emails.size());
            for (EmailAddress ea : emails) {
                emailClones.add((EmailAddress) ea.clone());
            }
            clone.setEmailAddresses(emailClones);
        }

        List<PostalAddress> postals = getPostalAddresses();
        if (postals != null) {
            List<PostalAddress> postalClones = new ArrayList<PostalAddress>(postals.size());
            for (PostalAddress pa : postals) {
                postalClones.add((PostalAddress) pa.clone());
            }
            clone.setPostalAddresses(postalClones);
        }

        List<PhoneNumber> numbers = getPhoneNumbers();
        if (numbers != null) {
            List<PhoneNumber> numberClones = new ArrayList<PhoneNumber>(numbers.size());
            for (PhoneNumber number : numbers) {
                numberClones.add((PhoneNumber) number.clone());
            }
            clone.setPhoneNumbers(numberClones);
        }

        return clone;

    }

    public EmailAddress getDefaultEmailAddress() {
        return (emailAddresses != null && !emailAddresses.isEmpty()) ? emailAddresses.get(0) : null;
    }

    public PostalAddress getDefaultPostalAddress() {
        return (postalAddresses != null && !postalAddresses.isEmpty()) ? postalAddresses.get(0) : null;
    }

    public PhoneNumber getDefaultPhoneNumber() {
        return (phoneNumbers != null && !phoneNumbers.isEmpty()) ? phoneNumbers.get(0) : null;
    }

    public void setDefaultEmailAddress(EmailAddress emailAddress) {
        add(emailAddress);
    }

    public void setDefaultPostalAddress(PostalAddress postalAddress) {
        add(postalAddress);
    }

    public void setDefaultPhoneNumber(PhoneNumber phoneNumber) {
        add(phoneNumber);
    }

    public abstract void accept(PartyVisitor visitor);

}



